package game.logic.app;

import game.logic.util.ShadowRenderer;
import net.miginfocom.swing.MigLayout;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;

public class GameBoard extends JLayeredPane
{
    private PuzzleGrid puzzleGrid;
    private BrickPanel brickPanel;
    private JScrollPane scrollPane;

    private ThumbnailPane thumbnailPane;
    private JPanel board;

    private PuzzleBrick selectedBrick;
    private boolean puzzleStarted;

    public GameBoard()
    {
        initUI();
    }

    private void initUI()
    {
        thumbnailPane = new ThumbnailPane();
        board = new JPanel(new MigLayout("", "[pref!][360!]", "[min][min]"));

        add(board, JLayeredPane.DEFAULT_LAYER);
        add(thumbnailPane, JLayeredPane.DRAG_LAYER);

        Font btnFont = UIManager.getFont("Button.font").deriveFont(18f);
        JToolBar toolBar = new JToolBar();
        JButton btn1 = new JButton("清空棋盘");
        btn1.setFont(btnFont);
        btn1.addActionListener(_ -> resetBoard());
        JButton btn2 = new JButton("开始题目");
        btn2.setFont(btnFont);
        btn2.addActionListener(_ -> startPuzzle());
        JButton btn3 = new JButton("重置题目");
        btn3.setFont(btnFont);
        btn3.addActionListener(_ -> resetPuzzle());
        JButton btn4 = new JButton("查看答案");
        btn4.setFont(btnFont);
        btn4.addActionListener(_ -> solvePuzzle());
        JButton btn5 = new JButton("☢随机题目☢");
        btn5.setFont(btnFont);
        btn5.setForeground(Color.decode("#CCCC66"));
        btn5.addActionListener(_ -> randomPuzzle());
        toolBar.add(btn1);
        toolBar.add(Box.createHorizontalStrut(20));
        toolBar.add(btn2);
        toolBar.add(Box.createHorizontalStrut(20));
        toolBar.add(btn3);
        toolBar.add(Box.createHorizontalStrut(20));
        toolBar.add(btn4);
        toolBar.add(Box.createHorizontalStrut(20));
        toolBar.add(btn5);

        puzzleGrid = new PuzzleGrid();
        brickPanel = new BrickPanel();

        scrollPane = new JScrollPane(brickPanel);
        scrollPane.getVerticalScrollBar().setUnitIncrement(16);
        board.add(toolBar, "spanx 2, wrap");
        board.add(puzzleGrid);
        board.add(scrollPane, "grow");

        configLayeredPane();
        configMouseActions();
        configKeyboardActions();
    }

    private void configLayeredPane()
    {
        addComponentListener(new ComponentAdapter()
        {
            @Override
            public void componentResized(ComponentEvent e)
            {
                board.setSize(getSize());
                thumbnailPane.setSize(getSize());
                revalidate();
            }
        });
        setPreferredSize(board.getPreferredSize());
        thumbnailPane.hideThumbnail();
    }

    private void configMouseActions()
    {
        brickPanel.addMouseListener(new MouseAdapter()
        {
            @Override
            public void mousePressed(MouseEvent e)
            {
                if (selectedBrick == null)
                    dragCandidateBrick(e);
                e.consume();
            }

            @Override
            public void mouseReleased(MouseEvent e)
            {
                if (selectedBrick != null)
                    dropBrick(e);
                e.consume();
            }
        });

        brickPanel.addMouseMotionListener(new MouseAdapter()
        {
            @Override
            public void mouseDragged(MouseEvent e)
            {
                if (selectedBrick != null)
                    moveBrick(e);
                e.consume();
            }
        });

        puzzleGrid.addMouseListener(new MouseAdapter()
        {
            @Override
            public void mousePressed(MouseEvent e)
            {
                if (selectedBrick == null)
                    dragPlacedBrick(e);
                e.consume();
            }

            @Override
            public void mouseReleased(MouseEvent e)
            {
                if (selectedBrick != null)
                    dropBrick(e);
                e.consume();
            }
        });
        puzzleGrid.addMouseMotionListener(new MouseAdapter()
        {
            @Override
            public void mouseDragged(MouseEvent e)
            {
                if (selectedBrick != null)
                    moveBrick(e);
                e.consume();
            }
        });
    }

    private void configKeyboardActions()
    {
        setFocusable(true);

        InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_DOWN_MASK), "reset-board");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_G, 0), "random-puzzle");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0), "reset-puzzle");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0), "start-puzzle");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0), "solve-puzzle");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F, 0), "rotate-brick");

        registerAction("reset-board", this::resetBoard);
        registerAction("random-puzzle", this::randomPuzzle);
        registerAction("start-puzzle", this::startPuzzle);
        registerAction("reset-puzzle", this::resetPuzzle);
        registerAction("solve-puzzle", this::solvePuzzle);
        registerAction("rotate-brick", this::rotateSelectedBrick);
    }

    private void registerAction(String key, Runnable action)
    {
        getActionMap().put(key, new AbstractAction()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                action.run();
            }
        });
    }

    private void resetBoard()
    {
        puzzleStarted = false;
        puzzleGrid.reset();
        brickPanel.reset();
        scrollPane.getVerticalScrollBar().setValue(0);
        revalidate();
        repaint();
    }

    private void randomPuzzle()
    {
        String puzzle = Solutions.randomPuzzle();
        if (puzzle == null)
        {
            JOptionPane.showMessageDialog(this, "无法生成题目，请自行摆放障碍");
            return;
        }

        brickPanel.removeAllBricks();
        brickPanel.addBricks(PuzzleBricks.normals());
        puzzleGrid.init(puzzle);
        revalidate();
        repaint();

        puzzleStarted = true;
    }

    private void startPuzzle()
    {
        if (puzzleGrid.getBarriersCount() != 3)
        {
            JOptionPane.showMessageDialog(this, "请先放置三个黑色障碍");
            return;
        }

        puzzleStarted = true;
    }

    private void resetPuzzle()
    {
        puzzleGrid.resetPuzzle();
        brickPanel.removeAllBricks();
        brickPanel.addBricks(PuzzleBricks.normals());
        revalidate();
        repaint();
    }

    private void solvePuzzle()
    {
        if (!puzzleStarted)
        {
            JOptionPane.showMessageDialog(this, "题目尚未开始");
            return;
        }

        String id = puzzleGrid.getPuzzleIdentity();
        String solution = Solutions.find(id);
        if (solution == null)
        {
            JOptionPane.showMessageDialog(this, "此题无解");
            return;
        }

        brickPanel.removeAllBricks();
        puzzleGrid.solve(solution);
        revalidate();
        repaint();
    }

    private void rotateSelectedBrick()
    {
        if (selectedBrick != null && !selectedBrick.isSquare())
        {
            selectedBrick.rotate();
            Point topLeft = thumbnailPane.updateThumbnail();
            SwingUtilities.convertPointFromScreen(topLeft, puzzleGrid);
            puzzleGrid.drawPossibleLocation(selectedBrick, topLeft);
        }
    }

    private void dragCandidateBrick(MouseEvent evt)
    {
        Component c = brickPanel.getComponentAt(evt.getPoint());
        if (c instanceof PuzzleBrick brick)
        {
            // 显示快照
            Point dragPoint = SwingUtilities.convertPoint(evt.getComponent(), evt.getPoint(), c);
            thumbnailPane.showThumbnail(brick, evt.getLocationOnScreen(), dragPoint);

            brickPanel.removeBrick(brick);
            selectedBrick = brick;
        }
    }

    private void dragPlacedBrick(MouseEvent evt)
    {
        Component c = puzzleGrid.getComponentAt(evt.getPoint());
        if (c instanceof PuzzleBrick brick)
        {
            if (brick.isBarrier() && puzzleStarted)
                return; // 题目开始后就不允许移动障碍了。

            // 显示快照
            Point dragPoint = SwingUtilities.convertPoint(evt.getComponent(), evt.getPoint(), brick);
            thumbnailPane.showThumbnail(brick, evt.getLocationOnScreen(), dragPoint);

            puzzleGrid.removeBrick(brick);
            selectedBrick = brick;
        }
    }

    private void dropBrick(MouseEvent evt)
    {
        Point p = SwingUtilities.convertPoint(evt.getComponent(), evt.getPoint(), puzzleGrid);

        // 在拼图区域落下方块
        if (puzzleGrid.contains(p))
        {
            Point topLeft = thumbnailPane.getTopLeftLocation();
            SwingUtilities.convertPointFromScreen(topLeft, puzzleGrid);

            if (!puzzleGrid.placeBrickAt(selectedBrick, topLeft))
            {
                // 该位置放置不了砖块，将砖块放回候选区（第一个）
                brickPanel.addBrick(selectedBrick);
                scrollPane.getVerticalScrollBar().setValue(0);
            }

            thumbnailPane.hideThumbnail();
            selectedBrick = null;

            if (puzzleGrid.isSolved())
                JOptionPane.showMessageDialog(this, "题目完成");

            return;
        }

        // 在其他区域落下方块
        brickPanel.addBrick(selectedBrick);
        scrollPane.getVerticalScrollBar().setValue(0);
        thumbnailPane.hideThumbnail();
        selectedBrick = null;
    }

    private void moveBrick(MouseEvent evt)
    {
        // 返回thumbnail（左上角）在屏幕上的坐标
        Point topLeft = thumbnailPane.moveThumbnail(evt.getLocationOnScreen());

        SwingUtilities.convertPointFromScreen(topLeft, puzzleGrid);
        puzzleGrid.drawPossibleLocation(selectedBrick, topLeft);
    }


    static class ThumbnailPane extends JComponent
    {
        private PuzzleBrick origin;
        private Point pointerLocation;
        private Point dragPoint;
        private BufferedImage image;
        private BufferedImage shadow;
        private boolean initialVertical;

        Point getTopLeftLocation()
        {
            if (origin == null)
                return null;

            boolean rotated = (initialVertical != origin.isVertical());
            int x = rotated ? pointerLocation.x - dragPoint.y : pointerLocation.x - dragPoint.x;
            int y = rotated ? pointerLocation.y - dragPoint.x : pointerLocation.y - dragPoint.y;
            Point topLeftBrickCenter = new Point(x + PuzzleBrick.UNIT_SIZE / 2, y + PuzzleBrick.UNIT_SIZE / 2);
            SwingUtilities.convertPointToScreen(topLeftBrickCenter, this);
            return topLeftBrickCenter;
        }

        Point updateThumbnail()
        {
            BufferedImage thumbnail = new BufferedImage(origin.getWidth(), origin.getHeight(), BufferedImage.TYPE_INT_ARGB);
            origin.paint(thumbnail.getGraphics());

            image = thumbnail;
            shadow = new ShadowRenderer(5, 0.3f, Color.BLACK).createShadow(thumbnail);
            repaint();

            boolean rotated = (initialVertical != origin.isVertical());
            int x = rotated ? pointerLocation.x - dragPoint.y : pointerLocation.x - dragPoint.x;
            int y = rotated ? pointerLocation.y - dragPoint.x : pointerLocation.y - dragPoint.y;
            Point topLeftBrickCenter = new Point(x + PuzzleBrick.UNIT_SIZE / 2, y + PuzzleBrick.UNIT_SIZE / 2);
            SwingUtilities.convertPointToScreen(topLeftBrickCenter, this);
            return topLeftBrickCenter;
        }

        Point moveThumbnail(Point point)
        {
            SwingUtilities.convertPointFromScreen(point, this);
            pointerLocation = point;
            repaint();

            boolean rotated = (initialVertical != origin.isVertical());
            int x = rotated ? pointerLocation.x - dragPoint.y : pointerLocation.x - dragPoint.x;
            int y = rotated ? pointerLocation.y - dragPoint.x : pointerLocation.y - dragPoint.y;
            Point topLeftBrickCenter = new Point(x + PuzzleBrick.UNIT_SIZE / 2, y + PuzzleBrick.UNIT_SIZE / 2);
            SwingUtilities.convertPointToScreen(topLeftBrickCenter, this);
            return topLeftBrickCenter;
        }

        void hideThumbnail()
        {
            origin = null;
            setVisible(false);
        }

        void showThumbnail(PuzzleBrick brick, Point locationInBoard, Point locationInThumbnail)
        {
            BufferedImage thumbnail = new BufferedImage(brick.getWidth(), brick.getHeight(), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = thumbnail.createGraphics();

            // 设置透明度
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f));
            brick.paint(g2d);
            g2d.dispose();

            origin = brick;
            initialVertical = brick.isVertical();
            image = thumbnail;
            shadow = new ShadowRenderer(5, 0.3f, Color.BLACK).createShadow(thumbnail);

            SwingUtilities.convertPointFromScreen(locationInBoard, this);
            pointerLocation = locationInBoard;
            dragPoint = locationInThumbnail;
            setVisible(true);
        }

        @Override
        protected void paintComponent(Graphics g)
        {
            if (image != null && pointerLocation != null)
            {
                boolean rotated = (initialVertical != origin.isVertical());
                int x = rotated ? pointerLocation.x - dragPoint.y : pointerLocation.x - dragPoint.x;
                int y = rotated ? pointerLocation.y - dragPoint.x : pointerLocation.y - dragPoint.y;

                g.drawImage(shadow, x, y, null);
                g.drawImage(image, x, y, null);
            }
        }
    }
}
